#include "PlayManagerImplementation.h"

#include "FMODSystem.h"

#include "api/inc/fmod.hpp"
#include "api/inc/fmod_errors.h"

#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <map>

namespace SnackBreak
{

PlayManager& PlayManager::Get() { return Impl::FMODPlayManager::Get(); };

namespace Impl
{

FMODPlayManager& FMODPlayManager::Get()
{
	// beware of static initialisation - I _think_ this is statically inited later than something global because it's in a routine
	// but that sounds like a dumb assumption
	// we may have to dynamically allocate this
	static Impl::FMODPlayManager thePlayManager;// = NULL;
	// A solution/improvement would be to have the playmanager make the fmodsystem (and hand it out too?)

	return thePlayManager;
}
FMOD::DSP* FMODPlayManager::dspObject = NULL;

FMOD_RESULT F_CALLBACK FMODPlayManager::AutomationCallback(
  FMOD_DSP_STATE *  dsp_state, 
  float *  inbuffer, 
  float *  outbuffer, 
  unsigned int  length, 
  int  inchannels, 
  int  outchannels
)
{
	// assuming we're fading in, calculate a start amp and end amp for this chunk

	// assuming everything's stereo (or at least, i==o) for now
	// which is fine forever, as we pass 0 for numchannels when creating
	assert(inchannels == outchannels);
	assert(inbuffer != NULL);
	assert(outbuffer != NULL);
	assert(dsp_state != NULL);
	assert(dsp_state->instance != NULL);
	if ((inchannels != outchannels) ||
		(inbuffer == NULL) ||
		(outbuffer == NULL) ||
		(dsp_state == NULL) ||
		(dsp_state->instance == NULL))
		return FMOD_OK; // yea, we're ok with that

	FMOD::DSP* dsp = reinterpret_cast<FMOD::DSP*>(dsp_state->instance);
	if (dsp != NULL)
	{
		FMODPlayManager* playMan;
		dsp->getUserData((void**)&playMan);
		if (playMan != NULL)
		{
			double bufferDuration = length / System::getSampleRate();
			for (AutoMixCollection::iterator it = playMan->automixes.begin(); it != playMan->automixes.end(); it++)
				(*it)->AutomationAction(bufferDuration);
		}
	}

	// identity DSP
	for (unsigned int frame = 0; frame < length; frame++) 
		for (int ch = 0; ch < outchannels; ch++) 
			outbuffer[(frame * outchannels) + ch] = inbuffer[(frame * inchannels) + ch];

	return FMOD_OK;
}

void FMODPlayManager::ArmDSP(FMODPlayManager* playMan)
{
	if (dspObject == NULL)
	{
		FMOD::DSPConnection* con;

		FMOD_DSP_DESCRIPTION myDSP;
		myDSP.channels = 0;
		myDSP.config = NULL;
		myDSP.configheight = 0;
		myDSP.configwidth = 0;
		myDSP.create = NULL;
		myDSP.release = NULL;
		myDSP.reset = NULL;
		myDSP.read = &AutomationCallback;
		myDSP.setposition = NULL;
		myDSP.paramdesc = NULL;
		myDSP.numparameters = 0;
		myDSP.setparameter = NULL;
		myDSP.getparameter = NULL;
		myDSP.userdata = playMan;

		System::Get().createDSP(&myDSP, &dspObject);	
		System::Get().addDSP(dspObject, &con);
		dspObject->setActive(true);
	}
}

void FMODPlayManager::KillDSP()
{
	if (dspObject != NULL)
	{
		dspObject->setActive(false);
		dspObject->disconnectAll(true, true);
		dspObject->release();
		dspObject = NULL;
	}
}

FMODPlayManager::FMODPlayManager() 
{
	ArmDSP(this);
}

FMODPlayManager::~FMODPlayManager() 
{
	KillDSP();

	for (MixCollection::iterator it = drops.begin(); it < drops.end(); it++)
		delete *it;
	drops.clear(); // for good measure
	for (MixMap::iterator it = mixes.begin(); it != mixes.end(); it++)
		delete it->second;
	mixes.clear(); // for good measure
	for (AutoMixCollection::iterator it = automixes.begin(); it != automixes.end(); it++)
		delete *it;
	automixes.clear(); // for good measure
	
	// really good idea - remove the tracks _after_ removing the events (as the event callbacks still may occur, and the tracks should be valid!)
	// i.e. events depend on tracks, so destroy events first (IMPORTANT!)
	for (TrackCollection::iterator it = tracks.begin(); it < tracks.end(); it++)
	{
		(*it)->Pause();	
		delete *it;
	}
	tracks.clear(); // for good measure

	std::cerr << "finished freeing playmanager" << std::endl;
}

FMODTrack* FMODPlayManager::CreateTrack(std::string f, CuePair q)
{
	tracks.push_back(FMODTrack::Create(f, q));
	return tracks.back();
}

FMODTrack* FMODPlayManager::CreateTrack(std::string f, CuePair q, double lastBeatNum)
{
	tracks.push_back(FMODTrack::Create(f, q, lastBeatNum));
	return tracks.back();
}

FMODMixEvent* FMODPlayManager::CreateMix(Track* outgoing, Track* incoming)
{
//	FMODMixEvent* ourEvent = NULL;
	DropEvent* ourEvent = NULL;
	FMODTrack* i = dynamic_cast<FMODTrack*>(incoming);
	FMODTrack* o = dynamic_cast<FMODTrack*>(outgoing);

	if (IsValid(o) && IsValid(i))
	{
		ourEvent = new DropEvent(o, i);

		drops.push_back(ourEvent);
	}

	return ourEvent;
}

FMODMixEvent* FMODPlayManager::CreateMix(Track* outgoing, Track* incoming, double numBeatsToOverlap)
{
//	FMODMixEvent* ourEvent = NULL;
	OverlapMixEvent* ourEvent = NULL;
	FMODTrack* i = dynamic_cast<FMODTrack*>(incoming);
	FMODTrack* o = dynamic_cast<FMODTrack*>(outgoing);

	if (IsValid(o) && IsValid(i))
	{
		ourEvent = new OverlapMixEvent(o, i, numBeatsToOverlap);

		mixes[ourEvent->getName()] = ourEvent;
	}

	return ourEvent;
}

FMODMixEvent* FMODPlayManager::CreateEarlyMix(Track* outgoing, Track* incoming, double numBeatsFromStart)
{
	assert(outgoing != NULL);
		
	return CreateMix(outgoing, incoming, outgoing->getLastBeatNum() - numBeatsFromStart);
}

FMODMixEvent* FMODPlayManager::CreateBlend(Track* outgoing, Track* incoming, double numBeatsToOverlap, double faderBlendBeats, double incomingStartBeat)
{
	assert(outgoing != NULL);
		
	// there is an issue of whether there will be a "composite" mix event (i.e. a crossfade event + overlap event) or whether 
	// will just be a new mixevent which does both.
	// worrying about this here as what the return value is is trickier if we support real composite events
	// but for now it'll just be a new mix class based on overlap. so that's easy.

	BlendMixEvent* ourEvent = NULL;
	FMODTrack* i = dynamic_cast<FMODTrack*>(incoming);
	FMODTrack* o = dynamic_cast<FMODTrack*>(outgoing);

	if (IsValid(o) && IsValid(i))
	{
		ourEvent = new BlendMixEvent(o, i, numBeatsToOverlap, faderBlendBeats, incomingStartBeat);

		//mixes[ourEvent->getName()] = ourEvent;
		automixes.push_back(ourEvent);
	}

	return ourEvent;
}

FMODMixEvent* FMODPlayManager::CreateEarlyBlend(Track* outgoing, Track* incoming, double numBeatsFromStart, double faderBlendBeats, double incomingStartBeat)
{
	assert(outgoing != NULL);
		
	return CreateBlend(outgoing, incoming, outgoing->getLastBeatNum() - numBeatsFromStart, faderBlendBeats, incomingStartBeat);
}

void FMODPlayManager::FindDropsOffChannel(FMOD::Channel* ch, MixCollection& aBunchOfMixes)
{
	for (MixCollection::iterator it = drops.begin(); it < drops.end(); it++)
		if ((*it)->Outgoing()->getChannel() == ch)
			aBunchOfMixes.push_back(*it);
}

void FMODPlayManager::FindMixesOffChannel(FMOD::Channel* ch, char* name, MixCollection& aBunchOfMixes)
{
	// with mixes, there could be lots and lots of them.
	// so we give each one a unique name (FMODMixEvent makes one up) and search for that
	// channel is a lil bit overkill but all good
	std::string whatsHisName(name);
	SyncEvent* looking = mixes[whatsHisName];
	if ((looking != NULL) && (looking->Outgoing()->getChannel() == ch))
		aBunchOfMixes.push_back(looking);
}

}; // namespace Impl
}; // namespace SnackBreak

